package com.example.forum_system.searchEngine;

import com.example.forum_system.searchEngine.titleList.TitleList;
import com.example.forum_system.searchEngine.wordList.WordIndex;
import com.example.forum_system.searchEngine.wordList.WordList;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * @BelongsProject: 0.forum_system
 * @BelongsPackage: com.example.forum_system.SE
 * @Author : 游在海里的鱼
 * @CreateTime : 2023-11-26  19:37
 * @Version : 1.0
 * @Description : 搜索引擎
 */

/*  搜索引擎
    1.这个类只有两个公开方法，已经放进接口里面了，自己去用。
    2.此引擎使用HanLP来解析文本，需要导入依赖：
        <!-- https://mvnrepository.com/artifact/com.hankcs/hanlp -->
        <dependency>
            <groupId>com.hankcs</groupId>
            <artifactId>hanlp</artifactId>
            <version>portable-1.8.4</version>
        </dependency>

 */
@Component
public class SearchEngine implements ISearchEngine{

    //单词表
    private static final WordList WORD_LIST = new WordList();
    //标题表（暂未使用）
    private static final TitleList TITLE_LIST = new TitleList();



    /*------------------------------------普--通--方--法------------------------------------------------------------------
     * @Desc  : 搜索，得到符合要求的文章的编号列表
     * @Param : [search]
     * @Return: java.util.List<java.lang.Integer>
    **/
    @Override
    public List<Integer> search(String searchString) throws Exception {
        //1.得到“排名”后的文章id列表
        List<Integer> articleList = ranking(searchString);
        //2.文章id列表
        return articleList;
    }


    /*------------------------------------普--通--方--法------------------------------------------------------------------
     * @Desc  : 解析，将文章拆为词保存进wordlist
     * @Param : [str, id]
     * @Return: int
    **/
    @Override
    public int addArticle(String article, int articleId){
        //控制台打印文章（可要可不要）
        System.out.println(article);
        //1.将传入文章拆分为词数组
        String[] wordArr = analysisString(article);
        //2.将词数组和文章id一起加进wordList，
        for(int i=0; i<wordArr.length; i++){
            WORD_LIST.addIndex(wordArr[i],articleId,i);
        }
        //3.返回本次解析的词数组长度
        return wordArr.length;
    }

    /*------------------------------------普--通--方--法------------------------------------------------------------------
     * @Desc  : 私有方法，将传入的字符串解析为词数组。
     *          采取的解析思路是：每个字符都作为一个词。这是一种不好的思路，只是暂时使用而已
     * @Param : [str]
     * @Return: char[]
    **/
    private String[] analysisString(String str){
         String[] ret = new String[str.length()];
         for(int i=0; i<ret.length; i++){
             ret[i] = ""+str.charAt(i);
         }
//        //1，使用handLP解析文章
//        List<Term> termList = HanLP.segment(str);
//        //2.将解析后的结果转成String数组
//        String[] ret = new String[termList.size()];
//        int i=0;
//        for (Term term : termList) {
//            ret[i++] = term.word;
//        }
        //3.返回返回数组
        return ret;
    }

    /*------------------------------------普--通--方--法------------------------------------------------------------------
     * @Desc  : 私有方法，排名方法，目前使用的是“全包含有顺序”，有待升级
     * @Param : [search]
     * @Return: java.util.List<java.lang.Integer>
     **/
    private List<Integer> ranking(String searchString) throws Exception {
        //1.将搜索词解析为词数组
        String[] wordArr = analysisString(searchString);
        //2.得到排名后的文章id集合
        Set<Integer> idSet = containsOne(wordArr );
        //3.将集合转成列表返回
        return new ArrayList<>(idSet);
    }

    /*------------------------------------普--通--方--法------------------------------------------------------------------
     * @Desc  : 排名算法1：包含一个搜索词。即文章包含搜索词“hello word”中的任一字符
     * @Param : [search]
     * @Return: java.util.Set<java.lang.Integer>
    **/
    private Set<Integer> containsOne(String[] wordArr){
        //1.定义返回集合-文章id
        Set<Integer> retSet = new TreeSet<>();
        //2.遍历搜索词数组，把含有此词的文章的id都添加进返回retSet
        for(String word:wordArr){
            //获得此词的文章id集合
            Set<Integer> idSet = WORD_LIST.getSet(word);
            //遍历集合，将所有元素添加进retSet集合
            for(int id:idSet){
                retSet.add(id);
            }
        }
        //3.返回一个列表
        return retSet;
    }

    /*------------------------------------普--通--方--法------------------------------------------------------------------
     * @Desc  : 排名算法2：包含完整搜索词。即文章包含搜索词“hello word”中的全部字符，如“ellloh ordw”
     * @Param : [search]
     * @Return: java.util.Set<java.lang.Integer>
     **/
    private Set<Integer> containsAll(String[] wordArr){
        //1.定义返回集合-文章id
        Set<Integer> retSet = new TreeSet<>();

        //2.找到idSet长度最小的那个Set
        //最小长度
        int minSize = Integer.MAX_VALUE;
        //最小idSet的word
        String minWord = null;
        //遍历所有搜索词
        for(String word:wordArr){
            //得到此词的文章id集合
            Set<Integer> idSet = WORD_LIST.getSet(word);

            //寻找idSet最小的word
            if(idSet.size()< minSize){
                minWord = word;
                minSize = idSet.size();
            }
        }

        //3. 遍历idSet长度最小的set的所有id
        for(int id: WORD_LIST.getSet(minWord)){
            boolean hasAll = true;
            //遍历所有搜索词，检查每个搜索词里的idSet都存在此id
            for(String word:wordArr){
                if(!WORD_LIST.hasArticle(word,id)){
                    //有一个搜索词不包含此id，则此id不满足全包含
                    hasAll=false;
                    break;
                }
            }
            //满足搜索词全包含的id，添加进retSet
            if(hasAll){
                retSet.add(id);
            }
        }

        //4.返回符合条件的id集合
        return retSet;
    }

    /*------------------------------------普--通--方--法------------------------------------------------------------------
     * @Desc  : 排名算法3：全包含且有顺序。即文章包含搜索词“hello word”
     * @Param : [search, searchString]
     * @Return: java.util.Set<java.lang.Integer>
    **/
    private Set<Integer> containsAllAndOrderly(String[] wordAll,String searchString) throws Exception {
        //1.定义返回集合-文章id
        Set<Integer> retSet = new TreeSet<>();

        //2.得到搜索词全包含的id集合
        Set<Integer> containsAllSet = containsAll(wordAll);

        //3.遍历搜索词全包含的id集合，找到其中符合顺序的文章id
        for(int id:containsAllSet){
            //3.1 此id的文章下，每个搜索词所处的位置
            Map<String,List<Integer>> word_location = new HashMap<>();
            for(String word:wordAll){
                WordIndex index = WORD_LIST.getIndex(word,id);
                word_location.put(word,index.getLocation());
            }

            //3.2 得到最长的词的位置
            int maxLocation = -1;
            for(String key:wordAll){
                for(int location:word_location.get(key)){
                    if(location>maxLocation){
                        maxLocation = location;
                    }
                }
            }

            //3.3 创建一个词数组，根据所有搜索词的位置，将搜索词加入到对应的词数组数组处
            //最长词所在的位置是个下标，所以要加1才是合理的词数组长度
            String[] words = new String[maxLocation+1];
            for(String word:wordAll){
                for(int location:word_location.get(word)){
                    words[location]=word;
                }
            }

            //3.4 将words的空位置用"$"替换
            for(int i=0; i<words.length; i++){
                if(words[i] == null){
                    words[i] = "$";
                }
            }

            //3.5 将词数组拼接成一个完整的字符串
            String article = "";
            for(String str:words){
                article += str;
            }

            //3.6检验完整字符串里是否包含搜索字符串，有则添加此id进返回集合
            if(article.contains(searchString)){
                retSet.add(id);
            }
        }
        //4.返回符合条件的id集合
        return retSet;
    }
}
